#ifndef _URI_HPP_
#define _URI_HPP_

#include "jhdefs.hpp"
#include "filesys/path.hpp"
using JHCPP::filesys::CPath;
#include <vector>
#include <string>
#include <utility>

NET_NAMESPACE_BEGIN

class CURI
{
public:
	typedef std::vector<std::pair<std::string, std::string> > QueryParameters;

	CURI();
	explicit CURI(const std::string& uri);
	explicit CURI(const char* uri);
	CURI(const std::string& scheme, const std::string& pathEtc);
	CURI(const std::string& scheme, const std::string& authority, const std::string& pathEtc);
	CURI(const std::string& scheme, const std::string& authority, const std::string& path, const std::string& query);
	CURI(const std::string& scheme, const std::string& authority, const std::string& path, const std::string& query, const std::string& fragment);
	CURI(const CURI& uri);
	CURI(const CURI& baseURI, const std::string& relativeURI);
	explicit CURI(const CPath& path);
	~CURI();
	CURI& operator = (const CURI& uri);
	CURI& operator = (const std::string& uri);
	CURI& operator = (const char* uri);

	void swap(CURI& uri);

	void clear();

	std::string toString() const;

	const std::string& getScheme() const;
	void setScheme(const std::string& scheme);

	const std::string& getUserInfo() const;
	void setUserInfo(const std::string& userInfo);

	const std::string& getHost() const;
	void setHost(const std::string& host);

	unsigned short getPort() const;
	void setPort(unsigned short port);

	std::string getAuthority() const;
	void setAuthority(const std::string& authority);
	
	const std::string& getPath() const;
	void setPath(const std::string& path);

	std::string getQuery() const;
	void setQuery(const std::string& query);	

	void addQueryParameter(const std::string& param, const std::string& val = "");

	const std::string& getRawQuery() const;
	void setRawQuery(const std::string& query);	

	QueryParameters getQueryParameters() const;
	void setQueryParameters(const QueryParameters& params);

	const std::string& getFragment() const;
	void setFragment(const std::string& fragment);

	void setPathEtc(const std::string& pathEtc);
	std::string getPathEtc() const;

	std::string getPathAndQuery() const;

	void resolve(const std::string& relativeURI);
	void resolve(const CURI& relativeURI);

	bool isRelative() const;
	bool empty() const;

	bool operator == (const CURI& uri) const;
	bool operator == (const std::string& uri) const;
	bool operator != (const CURI& uri) const;
	bool operator != (const std::string& uri) const;

	void normalize();
	void getPathSegments(std::vector<std::string>& segments);

	static void encode(const std::string& str, const std::string& reserved, std::string& encodedStr);
	static void decode(const std::string& str, std::string& decodedStr, bool plusAsSpace = false);

protected:
	bool equals(const CURI& uri) const;
	bool isWellKnownPort() const;
	unsigned short getWellKnownPort() const;

	void parse(const std::string& uri);
	void parseAuthority(std::string::const_iterator& it, const std::string::const_iterator& end);
	void parseHostAndPort(std::string::const_iterator& it, const std::string::const_iterator& end);
	void parsePath(std::string::const_iterator& it, const std::string::const_iterator& end);
	void parsePathEtc(std::string::const_iterator& it, const std::string::const_iterator& end);
	void parseQuery(std::string::const_iterator& it, const std::string::const_iterator& end);
	void parseFragment(std::string::const_iterator& it, const std::string::const_iterator& end);
	
	void mergePath(const std::string& path);
	void removeDotSegments(bool removeLeading = true);
	static void getPathSegments(const std::string& path, std::vector<std::string>& segments);
	void buildPath(const std::vector<std::string>& segments, bool leadingSlash, bool trailingSlash);

	static const std::string RESERVED_PATH;
	static const std::string RESERVED_QUERY;
	static const std::string RESERVED_FRAGMENT;
	static const std::string ILLEGAL;

private:
	std::string    m_scheme;
	std::string    m_userInfo;
	std::string    m_host;
	unsigned short m_port;
	std::string    m_path;
	std::string    m_query;
	std::string    m_fragment;
};

inline void swap(CURI& u1, CURI& u2)
{
	u1.swap(u2);
}

NET_NAMESPACE_END

#endif
